package simulacao;

// AgenteBot
// Java AgentBot.
// Autor: Alexandre Heideker - alexandre.heideker@ufabc.edu.br
// UFABC Jun/2009
//
import java.util.ArrayList;
import java.util.Collections;
import swarm.space.Grid2d;
import swarm.gui.Raster;

/**
AgenteBot

<p>
A Heatbug is an agent in a 2-dimensional world. A Heatbug has the following
behavior:

<dir>
Each Heatbug has its own ideal temperature, and a color that indicates the 
Heatbug's ideal temperature (more green for cool-loving Heatbugs, more yellow 
for warmth-loving Heatbugs).
</dir>

<dir>
O AgenteBot Ã© uma simulaÃ§Ã£o de robÃ´s em mapas desconhecidos utilizando
algoritmos de busca para aprendizado.
</dir>

<dir>
Agentes:
- ObstÃ¡culo (mapa)
posiÃ§Ã£o X,Y

- Robo:
posiÃ§ao x,y
Nome - nome do agente
TempoDoTrabalho - registra o tempo necessÃ¡rio para encontrar o alvo
GridMemoriaPessoal - registra a BC obtida pelo agente
GridMemÃ³riaPÃºblica - registra a BC obtida em trocas
Target - Objetivo escolhido entre os disponÃ­veis
AdicionaElementoBC - mÃ©todo para processar a BC obtida em interaÃ§Ãµes
tipo:
- Robo AMIGO
Fornece BC - troca informaÃ§Ãµes com outros robÃ´s (passam sua base de conhecimento)
Absorve BC - adiciona base de conhecimento obtida com robÃ´s amigos

- Robo NÃ£o Amigo
Fornece Dirt BC - Fornece BC falsa a fim de confundir AMIGO
- Objetivo
posiÃ§Ã£o x,y
nome

Base de conhecimento:
- Grid 2d com os elementos encontrados em cada contato.

Forma de carregar um mapa.....
ApÃ³s o mapa ser carregado, os objetivos sÃ£o e os agentes. Cada agente recebe aleatoriamente a missÃ£o
de encontrar um objeto.
A simulaÃ§Ã£o tem inicio e para quando todos os agentes robÃ´ alcanÃ§aram seu objetivo.

</dir>

 */
public class Bot {
    // Coordenas X,Y do bot:

    public int x, y;
    // Tempo para encontrar o alvo:
    public int tempo;
    public String nome = "";
    // armazena o ultimo movimento realizado
    private int ultimoMovimento = 4;
    // flag que indica que o robot deve buscar outro caminho
    public byte colorIndex;
    private Grid2d _world;
    private Memoria _memo;
    private BotModelSwarm _model;
    private Cliente Cli;

    public Bot(Grid2d world, BotModelSwarm model,
            int BotIndex, Memoria memoria) {
        _world = world;
        _model = model;
        _memo = memoria;

        if (_world == null) {
            System.err.println("O Bot nÃ£o tem um mundo!");
        }
        Cli=new Cliente();
    } /// constructor

    public Object drawSelfOn(Raster raster) {
        raster.drawPointX$Y$Color(x, y, colorIndex);
        return this;
    }

    private class Coordenada implements Comparable<Coordenada> {

        public int x;
        public int y;
        public int nota;
        public double distancia;
        public int indice = 4; //utilizado em A*
        public boolean vizitado = false; //utilizado em A*
        //public boolean possivel=false; //utilizado em A*

        public Coordenada(int x, int y) {
            this.x = x;
            this.y = y;
            this.nota = 1;
            this.distancia = 100000;
        }

        public Coordenada() {
            this.nota = 1;
            this.distancia = 100000;
        }

        public void setXY(int x, int y) {
            this.x = x;
            this.y = y;
            if (x < 0 || x >= _world.getSizeX() || y < 0 || y >= _world.getSizeY()) {
                this.nota = 0;
            }
        }

        public void setXY(int x, int y, int i) {
            setXY(x, y);
            this.indice = i;
        }

        public void setDistancia(int xFim, int yFim) {
            if (_memo.getValueAtX$Y(this.x, this.y) > 1 && this.nota > 0) {
                this.distancia = (double) Math.sqrt(Math.pow(Math.abs(this.x - xFim), 2) + Math.pow(Math.abs(this.y - yFim), 2));
            } else //hÃ¡ um bloqueio ou Ã© uma Ã¡rea desconhecida....
            {
                this.distancia = 100000;
            }

        }

        public boolean eIgual(Coordenada ponto) {
            if (ponto.x == this.x && ponto.y == this.y) {
                return true;
            } else {
                return false;
            }
        }

        public int compareTo(Coordenada ponto) {
            if (ponto.distancia > this.distancia) {
                return -1;
            } else {
                return 1;
            }
        }

        public String toString() {
            return String.format("x=%d y=%d  nota=%d  indice=%d  distancia=%f ", this.x, this.y, this.nota, this.indice, this.distancia);
        }
    }

    public synchronized void BotStep() {

        int newX, newY;
        Coordenada atual=new Coordenada();
        atual.x=this.x;
        atual.y=this.y;


        Coordenada xy;

        xy = Decisao();

        if (xy.nota > 0) {
            if (_memo.getValueAtX$Y(xy.x, xy.y) < 14) //incrementa o nÃºmero de passagens pelo local
            {
                _memo.putValue$atX$Y(_memo.getValueAtX$Y(xy.x, xy.y) + 1, xy.x, xy.y);
            }
            _world.putObject$atX$Y(null, this.x, this.y);
            this.x = xy.x;
            this.y = xy.y;
            _world.putObject$atX$Y(this, this.x, this.y);
        }
        Cli.send(comando(atual,xy));
    }

    private char comando(Coordenada atual, Coordenada nova){
        if (atual.x<nova.x) return 'L';
        if (atual.x>nova.x) return 'O';
        if (atual.y<nova.y) return 'S';
        if (atual.y>nova.y) return 'N';
        return 'F';



    }



    private Coordenada Decisao() {
        //este mÃ©todo escolhe entre as 4 possibilidades de movimento, o mais adequado
        //de acordo com as heurÃ­sticas escolhidas.
        //
        //

        Coordenada mv[] = new Coordenada[5];  //inicializa os caminho com nota 1 para cada um
        //Verifica os limites geogrÃ¡ficos dos possÃ­veis caminhos...
        //Caminhos fora dos limites geogrÃ¡ficos recebem nota 0
        for (int i = 0; i < 5; i++) {
            mv[i] = new Coordenada();
        }

        mv[0].setXY(x, y - 1);
        mv[1].setXY(x, y + 1);
        mv[2].setXY(x + 1, y);
        mv[3].setXY(x - 1, y);
        mv[4].setXY(x, y);
        /*
         * Primeiro passo Ã© verificar se o RobÃ´ jÃ¡ chegou ao objetivo
         */




        if (this.x == StartBot.saidaX && this.y == StartBot.saidaY) //RobÃ´ jÃ¡ chegou na saÃ­da
        {
            return mv[4];
        }

        int escolha = 5; //esta variÃ¡vel indica qual o caminho escolhido.

        // Esta simulaÃ§Ã£o opera de duas formas distintas:
        //  - busca cega mapeando o ambiente;
        //  - busca A* quando um dos agentes encontra a saÃ­da

        // a saÃ­da Ã© identificada quando uma das coordenadas possÃ­veis Ã©
        // igual a 0 ou igual a worldSize (jÃ¡ que o ambiente Ã© todo cercado)
        if (StartBot.saidaX >= 0) {
            //A saÃ­da foi encontrada....aplicar heurÃ­stica A*
            //Caso a busca A* nÃ£o contenha um caminho direto ao
            //objetivo, ou seja, o RobÃ´ encontra-se cercado por regiÃµes
            //desconhecidas, ele deve continuar na busca cega...
            //
            // Caso exista um caminho adequado, a variÃ¡vel "escolha" Ã© setada.

            escolha = AEstrela(this.x, this.y, StartBot.saidaX, StartBot.saidaY);

//        escolha=TracaRota(x,y,StartBot.saidaX,StartBot.saidaY);


        }
        if (escolha > 3) { //saÃ­da nÃ£o conhecida...explorar ambiente
            //prÃ³ximo passo Ã© avaliar os caminhos que sÃ£o vÃ¡lidos,
            //ou seja, sem muros ou outros RobÃ´s no caminho.
            //Caminhos com muros sÃ£o registrados na memÃ³ria.
            escolha = 4;
            for (int i = 0; i < 4; i++) {
                if (mv[i].nota > 0) {
                    if (_world.getObjectAtX$Y(mv[i].x, mv[i].y) != null) {
                        //HÃ¡ algo no caminho - vou verificar se Ã© um muro ou
                        //outro RobÃ´
                        mv[i].nota = 0; //caminho bloqueado
                        if (!_world.getObjectAtX$Y(mv[i].x, mv[i].y).toString().equalsIgnoreCase("bot")) {
                            //nÃ£o Ã© um RobÃ´....vamos registrar o muro na memÃ³ria
                            _memo.putValue$atX$Y(1, mv[i].x, mv[i].y);
                        } else {
                            if (_memo.getValueAtX$Y(mv[i].x, mv[i].y) == 0) {
                                _memo.putValue$atX$Y(2, mv[i].x, mv[i].y);
                            }
                        }
                    } else {
                        if (mv[i].x == 0 || mv[i].x == _world.getSizeX() - 1 || mv[i].y == 0 || mv[i].y == _world.getSizeY() - 1) {
                            // Encontrada a saÃ­da!
                            mv[i].nota = 50;
                            _memo.putValue$atX$Y(50, mv[i].x, mv[i].y);
                            StartBot.saidaX = mv[i].x;
                            StartBot.saidaY = mv[i].y;
                        } else {
                            //NÃ£o hÃ¡ nada no caminho, vou atribuir uma nota a este caminho
                            //de acordo com as heurÃ­sticas disponÃ­veis
                            if (_memo.getValueAtX$Y(mv[i].x, mv[i].y) == 0) {
                                _memo.putValue$atX$Y(2, mv[i].x, mv[i].y);
                            }
                            mv[i].nota = 15 - _memo.getValueAtX$Y(mv[i].x, mv[i].y);
                            if (i == this.ultimoMovimento) //crÃ©dito para o Ãºltimo movimento
                            {
                                mv[i].nota++;
                            }
                        }
                    }
                }
                if (mv[i].nota > mv[escolha].nota) {
                    escolha = i;
                }
            }
            if (mv[escolha].nota > 0) {
                this.ultimoMovimento = escolha;
            }
        }
        return mv[escolha];
    }

    private class No implements Comparable<No> {

        public int Dist;
        public int Cust;
        public int Heu;
        public No Pai;
        public int x;
        public int y;

        public No(int x, int y) {
            this.x = x;
            this.y = y;
        }

        public int compareTo(No outro) {
            if (outro.Heu > this.Heu) {
                return -1;
            } else if (outro.Heu < this.Heu) {
                return 1;
            } else {
                return 0;
            }
        }

        public void setDist(int xf, int yf) {
            //seta a distância de Manhatan até o ponto final
            this.Dist = Math.abs(xf - this.x) + Math.abs(yf - this.y);
        }

        public void setHeu(int xf, int yf, int custo) {
            this.setDist(xf, yf);
            this.Cust = custo;
            this.Heu = this.Cust + this.Dist;
        }

        public String toString() {
            return String.format("x=%d y=%d  Custo=%d  Dist.=%d  Heurística=%d ", this.x, this.y, this.Cust, this.Dist, this.Heu);
        }

        public boolean Equall(No no) {
            if (no.x == this.x && no.y == this.y) {
                return true;
            } else {
                return false;
            }
        }
    }

    public int AEstrela(int x1, int y1, int x2, int y2) {

        //
        ArrayList<No> cache;
        ArrayList<No> rota;
        No no, tmp;

        //Inicializando as ArrayList
        cache = new ArrayList<No>();
        rota = new ArrayList<No>();

        int filaX[] = {0, 0, 1, -1};
        int filaY[] = {-1, 1, 0, 0};

        //Adiciona o nó inicial ao cache
        no = new No(x1, y1);
        no.setHeu(x2, y2, 1);
        no.Pai = null;
        cache.add(no);
        boolean cy = true;
        do {
            Collections.sort(cache);
            if (cache.size() > 0) {
                no = cache.get(0);
                rota.add(cache.remove(0));
            }
            if (!(no.x == x2 && no.y == y2)) {

                for (int i = 0; i < 4; i++) {
                    tmp = new No(no.x + filaX[i], no.y + filaY[i]);
                    //System.out.println(tmp);
                    if (tmp.x >= 0 && tmp.y >= 0 && tmp.x < _world.getSizeX() && tmp.y < _world.getSizeY()) {
                        //coordenadas válidas...verifica existência do caminho
                        if (_memo.getValueAtX$Y(tmp.x, tmp.y) > 1) {
                            //Caminho disponível
                            tmp.setHeu(x2, y2, no.Cust + 1);
                            tmp.Pai = no;
                            //vamos verificar se este ponto está em uma das listas
                            boolean b = true;
                            for (int j = 0; j < rota.size(); j++) {
                                if (tmp.Equall(rota.get(j))) {
                                    b = false;
                                    break;
                                }
                            }
                            if (b) {
                                for (int j = 0; j < cache.size(); j++) {
                                    if (tmp.Equall(cache.get(j))) {
                                        b = false;
                                        break;
                                    }
                                }
                            }
                            if (b) {
                                cache.add(tmp);
                            }

                        }
                    }
                }

            } else {
                cy = false;
            }
        } while (cache.size() > 0 && cy);
        int tempX = x1, tempY = y1;
        if (rota.size() > 0) {
            no = rota.get(rota.size() - 1);
            //O primeiro nó da lista deve ser o alvo. Caso contrário, não há caminho
            //conhecido até o alvo.
            if (no.x == x2 && no.y == y2) {
                while (no.Pai != null) {
                    tempX = no.x;
                    tempY = no.y;
                    //System.out.println(no);
                    no = no.Pai;
                }
            } else {
                return 4;
            }

            //System.out.println(no);

            if (tempY < y1) {
                return 0;
            }
            if (tempY > y1) {
                return 1;
            }
            if (tempX > x1) {
                return 2;
            }
            if (tempX < x1) {
                return 3;
            }


        }

        return 4;
    }

    public Object putAtX$Y(int inX, int inY) {
        x = inX;
        y = inY;
        _world.putObject$atX$Y(this, x, y);
        return this;
    }

    public String toString() {
        return "bot";
    }
}
